//==============================================================================
//
//            DDirectory - the directory class in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-07-23 18:59:07 $ $Revision: 1.15 $
//
//==============================================================================

#include "ofc/DDirectory.h"

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#ifdef WIN32
#include <windows.h>
#include <ctype.h>
#else
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#endif


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DText.h"
#include "ofc/DList.h"
#include "ofc/DTextFilterable.h"

//
// The DDirectory class implements a number of methods for using directory paths.
// The paths are not checked by the ofc-library; this is left to the underlying os-specific
// functions. This class has also a number of class methods for creating, deleting, renaming 
// and scanning directories.
//
// @example
// #include <stdio.h>
// #include "ofc/DDirectory.h"
// 
// int main(int argc, char *argvp[])
// {
//   DDirectory    *dir1;
//   DList         *subs;
//   DListIterator *iter = [DListIterator new];
//   DText          *str;
// 
//   if ([DDirectory create :"sub"])      // Create a directory
//     printf("Directory \"sub\" succesfully created.\n");
//   else
//     printf("Directory \"sub\" could not be created:%d.\n", [DDirectory error]);
// 
//   if ([DDirectory exist :"sub"])       // Check for directory
//     printf("Directory \"sub\" exists.\n");
//   else
//     printf("Directory \"sub\" does noet exist:%d.\n", [DDirectory error]);
// 
//   if ([DDirectory move :"sub" :"renamed"])  // Rename directory
//     printf("Directory \"sub\" renamed to \"renamed\".\n");
//   else
//     printf("Directory \"sub\" could not be renamed to \"renamed\":%d.\n", [DDirectory error]);
// 
//   if ([DDirectory remove :"renamed"])   // Remove directory
//     printf("Directory \"renamed\" removed.\n");
//   else
//     printf("Directory \"renamed\" cound not be \"renamed\":%d.\n", [DDirectory error]);
// 
// 
//   dir1 = [DDirectory current];         // Get the current directory
// 
//   if ([dir1 parent])
//     printf("Move to parent succesfull:%s.\n", [dir1 path]);
//   else
//     printf("Move to parent failed.\n");
// 
//   subs = [DDirectory childs :[dir1 path] :nil]; // Iterate all sub directories
// 
//   if (subs != nil)
//   {
//     printf("Sub directories (%ld):\n", [subs length]);
// 
//     [iter list :subs];
// 
//     str = [iter first];
//     while (str != nil)
//     {
//       printf("  %s\n", [str cstring]);
//       str = [iter next];
//     }
//     [iter free];                         // Cleanup iter and list
//     [subs free];
//   }
//   else
//     printf("No subdirectories in the parent directory ?!\n");
// 
//   [dir1 free];                         // Cleanup directory
// 
//   return 0;
// }
// 

@interface DDirectory : DObject
{
@private
  DText          *_path;    // the path (and name) of the directory
}

#endif



@implementation DDirectory


#if _PRIVATE_

#ifndef PATH_MAX
  #ifdef MAXPATHLEN
    #define PATH_MAX  MAXPATHLEN
  #else
    #define PATH_MAX  512
  #endif
#endif

#define UNIX_SEP   '/'
#define WIN32_SEP  '\\'
#define DRIVE_SEP  ':'

static int _derror = 0;

static void skipTailingSeparator(DText *path)
{
  if ([path length] > 1)
  {
    char lastCh = [path get :-1];
    char prevCh = [path get :-2];
        
    if (([DDirectory isPathSeparator :lastCh]) && 
        (![DDirectory isPathSeparator :prevCh]) &&
        (![DDirectory isDriveSeparator :prevCh]))
    {
      [path pop];
    }
  }
}

static long findLastName(DText *path)
{
  long index  = 0;
  long name   = 0;
  long length = [path length];
  
  while (index < length)
  {
    char ch = [path get :index];
    
    if (([DDirectory isPathSeparator :ch]) || ([DDirectory isDriveSeparator :ch]))
    {
      name = index+1;
    }
      
    index++;
  }

  if (name >= length)
  {
    name = -1;
  }
  
  return name;
}

#endif


//// Constructors

//
// Initialise an empty directory object
//
// @return the object
//

- (DDirectory *) init
{
  [super init];

  _path   = [DText new];
  
  return self;
}

//
// Initialise the object with a directory path
//
// @return the object
//

- (DDirectory *) init :(const char *) path
{
  [self init];

  [self path :path];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object
//
// @return the object
//
- shallowCopy
{
  DDirectory *copy = [super shallowCopy];
  
  copy->_path = [_path copy];
  
  return copy;
}


//// Deconstructor

//
// Free the object
//
// @return the file object
//

- free
{
  [_path free];
  
  return [super free];
}


//// Class methods

//
// Check if the character is a drive separator
// (there is no check for misuse of ':' on WIN32)
// 
// @param ch           the character
// 
// @return is it a drive separator ?
// 

+ (BOOL) isDriveSeparator :(char) ch
{
#ifdef WIN32
  return (ch == DRIVE_SEP);
#else
  return NO;
#endif
}

//
// Check if the character is a path separator 
// 
// @param ch           the character 
// 
// @return is it a path separator ?
// 

+ (BOOL) isPathSeparator :(char) ch
{
#ifdef WIN32
  return ((ch == UNIX_SEP) || (ch == WIN32_SEP));
#else
  return (ch == UNIX_SEP);
#endif
}

//
// Create a directory
// 
// @param path         the path
// 
// @return success
// 

+ (BOOL) create :(const char *) path
{
  BOOL ok = NO;
  
  if (path == NULL)
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    ok = CreateDirectory(path, NULL);
    
    if (!ok)
    {
      _derror = GetLastError();
    }
#else
    ok = (mkdir(path, 0777) == 0);
    
    if (!ok)
    {
      _derror = errno;
    }
#endif
  }
  return ok;
}

//
// Move/Rename a directory
// 
// @param path         the current path
// @param newPath      the new path
// 
// @return success
// 

+ (BOOL) move :(const char *) path :(const char *) newPath
{
  BOOL ok = NO;
  
  if (path == NULL)
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else if (newPath == NULL)
  {
    WARNING(DW_INVALID_ARG, "newPath");
  }
  else
  {
#ifdef WIN32
    ok = MoveFile(path, newPath);
    
    if (!ok)
    {
      _derror = GetLastError();
    }
#else
    ok = (rename(path, newPath) == 0);
    
    if (!ok)
    {
      _derror = errno;
    }
#endif
  }
  
  return ok;
}

// 
// Remove a directory
// 
// @param path         the path 
// 
// @return success
// 

+ (BOOL) remove :(const char *) path
{
  BOOL ok = NO;
  
  if (path == NULL)
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    ok = RemoveDirectory(path);
    
    if (!ok)
    {
      _derror = GetLastError();
    }
#else
    ok = (rmdir(path) == 0);
    
    if (!ok)
    {
      _derror = errno;
    }
#endif
  }
  
  return ok;
}

//
// Return the current working directory
// 
// @return a (new) directory object with the current directory (or nil)
// 

+ (DDirectory *) current
{
  DDirectory *path = nil;

#ifdef WIN32
  char buf[MAX_PATH];
  
  if (GetCurrentDirectory(sizeof(buf), buf) > 0)
  {
    path = [DDirectory alloc];
    
    [path init :buf];
  }  
  else
  {
    _derror = GetLastError();
  }
#else
  char buf[PATH_MAX];
  char *result = NULL;
  
  result = getcwd(buf, sizeof(buf));
  
  if (result != NULL)
  {
    path = [DDirectory alloc];
    
    [path init :result];
  }
  else
  {
    _derror = errno;
  }
#endif
  
  return path;
}

//
// Set the current working directory
// 
// @param path         the path for the current directory
// 
// @return success
//

+ (BOOL) current :(const char *) path
{
  BOOL ok = NO;
  
  if (path == NULL)
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    ok = SetCurrentDirectory(path);
    
    if (!ok)
    {
      _derror = GetLastError();
    }
#else
    ok = (chdir(path) == 0);
    
    if (!ok)
    {
      _derror = errno;
    }
#endif
  }
  
  return ok;
}

//
// Return the temp directory
// 
// @return a (new) DDirectory object with the temp directory path (or nil)
// 

+ (DDirectory *) temp
{
  DDirectory *temp = nil;
  
#ifdef WIN32
  char buf[MAX_PATH];
  
  if (GetTempPath(sizeof(buf), buf) > 0)
  {
    temp = [DDirectory alloc];
    
    [temp init :buf];
  }
  else
  {
    _derror = GetLastError();
  }
#else
  temp = [DDirectory alloc];
  
  [temp init :"/tmp"];
#endif
  
  return temp;
}

//
// Check if the directory exist
// 
// @param path         the directory path
// 
// @return does it ?
// 

+ (BOOL) exist :(const char *) path
{
  BOOL ok = NO;
  
  if (path == NULL)
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    DWORD attributes = GetFileAttributes(path);
    
    if (attributes != INVALID_FILE_ATTRIBUTES)
    {
      ok = ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
    }
    else
    {
      _derror = GetLastError();
    }
#else
    struct stat info;
    
    ok = (stat(path, &info) == 0);
    
    if (ok)
    {
      ok = S_ISDIR(info.st_mode);
    }
    else
    {
      _derror = errno;
    }
#endif
  }
  
  return ok;
}

//
// Return a list with the filenames in the directory
// 
// @param path         the directory path
// @param filter       the filter for the names (or nil for no filter)
// 
// @return a (new) list met DText objects (or nil)
// 

+ (DList *) childs :(const char *) path :(id <DTextFilterable>) filter
{
  DList *list = nil;
  
#ifdef WIN32
  WIN32_FIND_DATA entry;
  HANDLE          dir;
  DDirectory     *epath = [DDirectory alloc];  
  
  [epath init :path];
  [epath child :"*.*"];
  
  dir = FindFirstFile([epath path], &entry);
  
  if (dir != INVALID_HANDLE_VALUE)
  {
    BOOL ok = YES;
    
    list = [DList new];
    
    do
    {
      if ((filter == nil) || ([filter accept :entry.cFileName]))
      {
        DText *name = [DText alloc];
        
        [name init :entry.cFileName];
        
        [list append :name];
      }
      
      ok = FindNextFile(dir, &entry);
    }
    while (ok);
    
    if (GetLastError() != ERROR_NO_MORE_FILES)
    {
      _derror = GetLastError();
    }
    
    FindClose(dir);
  }
  else
  {
    _derror = GetLastError();
  }
  [epath free];  
#else
  DIR *dir = opendir(path);
  
  if (dir != NULL)
  {
    struct dirent *entry;
    
    list = [DList new];
    
    entry = readdir(dir);
    
    while (entry != NULL)
    {
      if ((filter == nil) || ([filter accept :entry->d_name]))
      {
        DText *name = [DText alloc];
        
        [name init :entry->d_name];
        
        [list append :name];
      }
      
      entry = readdir(dir);
    }
    
    closedir(dir);
  }
#endif
  return list;
}

//
// Return the last error for the class methods
// 
// @return the last error
// 

+ (int) error
{
  return _derror;
}


//// Member methods

//
// Check if the directory has an absolute path
// 
// @return is the path absolute ?
// 

- (BOOL) isAbsolute
{
  BOOL absolute = NO;
  long length   = [_path length];

  if (length >= 1)
  {
    char first = [_path get :0];
    
    if ([DDirectory isPathSeparator :first])
    {
      absolute = YES;
    }
#ifdef WIN32
    else
    {
      if (length >= 3)
      {
        char second = [_path get :1];
        char third  = [_path get :2];
  
        if (([DDirectory isDriveSeparator :second])  && ([DDirectory isPathSeparator :third]))
        {
          absolute = YES;
        }
      }
    }
#endif
  }

  return absolute;
}

//
// Check if the directory path is empty
// 
// @return is it ?
// 

- (BOOL) isEmpty
{
  return [_path isEmpty];
}

//
// Return the drive letter
// 
// @return the drive letter (or EOS if not present)
// 

- (char) drive
{
  char drive = EOS;

#ifdef WIN32
  if ([_path length] >= 2)
  {
    if ([DDirectory isDriveSeparator :[_path get :1]])
    {
      drive = [_path get :0];
      
      if (!isalpha(drive))
      {
        drive = EOS;
      }
    }
  }
#endif
  
  return drive;
}

//
// Set the path for the directory
// 
// @param path      the path for the directory
// 
// @return the object
//

- (DDirectory *) path :(const char *) path
{
  if (path == NULL)
  {
    [_path clear];
  }
  else
  {
    [_path set :path];
    
    [_path strip];
    
    skipTailingSeparator(_path);
  }
  
  return self;
}

//
// Return the path (and name) of the directory
//
// @return the path
//

- (const char *) path
{
  return [_path cstring];
}

//
// Return the directory name in the path
// 
// @return (a reference to) the name (or NULL)
//

- (const char *) name
{
  const char *name = NULL;
  
  if ([_path length] > 0)
  {
    int index = findLastName(_path);
    
    if (index >= 0)
    {
      name = [_path cstring] + index;
    }
    else
    {
      name = NULL;
    }
  }

  return name;
}


//
// Add a subdirectory to the directory object
// 
// @param name      the name of the subdirectory
// 
// @return the object
// 

- (DDirectory *) child :(const char *) name
{
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else
  {
    if ([_path length] > 0)
    {
      while ([DDirectory isPathSeparator :*name])
      {
        name++;
      }
    
      if (*name != EOS)
      {
        char last = [_path get :-1];

        if ((![DDirectory isPathSeparator  :last]) &&
            (![DDirectory isDriveSeparator :last]))
        {
#ifdef WIN32
          [_path push :WIN32_SEP];
#else
          [_path push :UNIX_SEP];
#endif
        }
      }
    }
    
    [_path append :name];
    
    skipTailingSeparator(_path);
  }
  
  return self;
}

//
// Move the directory path to the parent directory
// 
// @return was there a parent directory ?
// 

- (BOOL) parent
{
  BOOL ok = NO;
  
  long name = findLastName(_path);
  
  if (name >= 0)
  {
    [_path delete :name :-1];
    
    skipTailingSeparator(_path);
    
    ok = YES;
  }
  
  return ok;
}

//
// Split up the path in a list of directory names
// 
// @return a (new) list with the the names (in DText objects)
// 

- (DList *) names
{
  long   start  = 0;
  long   end    = -1;
  long   index  = 0;
  long   length = [_path length];
  DList *names  = [DList new];
  
  // first store the root
#ifdef WIN32
  if (length >= 2)
  {
    char   first  = [_path get :0];
    char   second = [_path get :1];
    
    if ([DDirectory isDriveSeparator :second])
    {
      end = 1;

      if (length > 2)
      {
        char third = [_path get :2];

        if ([DDirectory isPathSeparator :third])
        {
          end = 2;
        }
      }
    }
    else if ([DDirectory isPathSeparator :first])
    {
      if ([DDirectory isPathSeparator :second])
      {
        end = 1;
      }
      else
      {
        end = 0;
      }
    }
    
  }
#else
  if (length > 0)
  {
    char ch = [_path get :0];
    
    if ([DDirectory isPathSeparator :ch])
    {
      end = 0;
    }
  }
#endif
  if (end != -1)
  {
    DText *root = [_path get :0 :end];
    
    [names append :root];
      
    index = end + 1;
    start = index;
    end   = -1;
  }
  
  while (index < length)
  {
    char ch = [_path get :index];

    if ([DDirectory isDriveSeparator :ch])
    {
      start = index + 1;
    }
    else if ([DDirectory isPathSeparator :ch])
    {
      end = index - 1;
      if (end - start < 0)
      {
        start = index + 1;
      }
      else
      {
        DText *name = [_path get :start :end];
        
        [names append :name];
        
        start = index + 1;
      }
    }
      
    index++;
  }

  end = index - 1;
  if (end - start >= 0)
  {
    DText *name = [_path get :start :end];
    
    [names append :name];
  }

  return names;
}

//
// Build the path with a list of directory names
// 
// @param names        a list of objects that respond to the textable protocol
// 
// @return the object
// 

- (DDirectory *) names :(DList *) names
{
  [_path clear];
  
  if (names != nil)
  {
    DListIterator *iter   = [DListIterator alloc];
    id             object = nil;
    
    [iter init :names];
    
    object = [iter first];
    while (object != nil)
    {
      if ([object conformsTo :@protocol(DTextable)])
      {
        DText *string = [object toText];
        
        [self child :[string cstring]];
        
        [string free];
      }
      
      object = [iter next];
    }
    
    [iter free];
  }
  
  return self;
}

@end

/*===========================================================================*/

